package org.datascooter.bundle.attribute;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.Date;

import org.datascooter.utils.DBType;
import org.datascooter.utils.DSSettings;
import org.datascooter.utils.LangUtils;
import org.datascooter.utils.TypeUtils;
import org.datascooter.utils.policy.ParamClass;

/**
 * Abstract class assumed attribute may be stored in a database so it has a column and other standart requisites
 * 
 * @author nemo
 * 
 */
public abstract class PersistAttribute extends AbstractAttribute {

	protected DBType type;
	private String column;
	private String columnPrefix = "";
	private String columnName;
	protected int scale = 0;
	protected int precision = 0;
	protected boolean nullable = true;
	protected boolean insertable = true;
	protected boolean updatable = true;
	private String columnDefinition;
	private boolean trimExtraString;

	public abstract <T extends PersistAttribute> T copyMod(DBType dbType);

	protected abstract Object invoke(Object obj) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;

	protected abstract void revoke(Object obj, Object param) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException;

	public PersistAttribute(String name, DBType type, String column, int scale, int precision, boolean nullable, boolean insertable, boolean updatable) {
		super(name);
		this.setColumn(LangUtils.defaultIfEmpty(column == null ? "" : column.toUpperCase(), name.toUpperCase()));
		this.setType(type);
		this.scale = scale;
		this.precision = precision;
		this.insertable = insertable;
		this.updatable = updatable;
		this.nullable = nullable;
	}

	@Override
	protected void setGetClass(Class<?> clazz) {
		super.setGetClass(clazz);
		if (type != null) {
			if (scale == 0) {
				scale = TypeUtils.getDefaultScale(type);
			}
			if (precision == 0) {
				precision = TypeUtils.getDefaultPrecision(type);
			}
		}
		trimExtraString = DSSettings.isTrimExtraString();
	}

	@SuppressWarnings("rawtypes")
	@Override
	public Object getValue(Object obj) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		Object x = invoke(obj);
		if (x != null) {
			if (!primitive) {
				x = x.toString();
			} else if (setParamClass.equals(ParamClass.ds_String)) {
				if (trimExtraString && (scale < ((String) x).length())) {
					x = ((String) x).substring(0, scale);
				}
			} else if (setParamClass.equals(ParamClass.ds_Date)) {
				x = new java.sql.Timestamp(((Date) x).getTime());
			} else if (setParamClass.equals(ParamClass.ds_enum)) {
				x = ((Enum) x).name();
			}
		}
		return x;
	}

	@SuppressWarnings("unchecked")
	@Override
	public void setValue(Object obj, Object param) throws SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException, ParseException {
		if (setParamClass.equals(ParamClass.ds_enum) && param != null) {
			revoke(obj, Enum.valueOf(setClass, (String) param));
		} else {
			if (!primitive) {
				Constructor<?> constructor = setClass.getConstructor(String.class);
				if (constructor == null) {
					return;
				}
				param = constructor.newInstance(param);
			}
			revoke(obj, TypeUtils.getTypeInstance(setParamClass, param));
		}
	}

	public void setType(DBType type) {
		this.type = type;
	}

	public DBType getType() {
		return type;
	}

	public int getScale() {
		return scale;
	}

	public void setScale(int scale) {
		this.scale = scale;
	}

	public int getPrecision() {
		return precision;
	}

	public void setPrecision(int precision) {
		this.precision = precision;
	}

	public void setColumn(String column) {
		this.column = column;
		columnName = columnPrefix + column;
	}

	public String getColumn() {
		return column;
	}

	public String getColumnName() {
		return columnName;
	}

	public void setColumnPrefix(String columnPrefix) {
		this.columnPrefix = columnPrefix;
		columnName = columnPrefix + column;
	}

	public boolean isInsertable() {
		return insertable;
	}

	public void setInsertable(boolean insertable) {
		this.insertable = insertable;
	}

	public boolean isUpdatable() {
		return updatable;
	}

	public void setUpdatable(boolean updatable) {
		this.updatable = updatable;
	}

	public void setNullable(boolean nullable) {
		this.nullable = nullable;
	}

	public boolean isNullable() {
		return nullable;
	}

	public void setColumnDefinition(String columnDefinition) {
		this.columnDefinition = columnDefinition;
	}

	public String getColumnDefinition() {
		return columnDefinition;
	}

	public void setColumnName(String columnName) {
		this.columnName = columnName;
	}
}
